(* open IntInf; *)

fun read file =
    let
        val inStream = TextIO.openIn file
    in
        (* TextIO.inputAll returns a TextIO.vector, which is a string. *)
        TextIO.inputAll inStream
    end;

val newline_tokenizer = String.tokens (fn c => c = #"\n");


fun sum_lists (nil, nil) = []
  | sum_lists (nil, l2) = []
  | sum_lists (l1, nil) = []
  | sum_lists (alst as (a::arest), blst as (b::brest)) =
    (a + b) :: sum_lists(arest, brest);


fun string_to_bits str =
    map (fn c =>
            case c of
                #"1" => 1
              | #"0" => 0
              | _ => raise Fail "unrecognized bit value")
        (String.explode str);


fun invert 0 = 1
  | invert 1 = 0
  | invert _ = raise Fail "unrecognized bit value";
fun invert_list lst = map invert lst;


fun make_list (0, _) = []
  | make_list (len, fill) =
    fill :: make_list (len - 1, fill);


fun int_expt (0, _) = 0
  | int_expt (1, _) = 1
  | int_expt (base, 0) = 1
  | int_expt (base, 1) = base
  | int_expt (base, exponent) =
    base * int_expt (base, exponent - 1);


fun bit_to_decimal (bit, ind) =
    bit * int_expt (2, ind);


fun bits_to_decimal bit_vector =
    let
        val len = Vector.length bit_vector;
        fun iter (bits, ind, acc) =
            if ind < len
            then
                let
                    val bit = Vector.sub(bits, ind);
                    val position = (len - 1) - ind;
                    val decimal_value = bit_to_decimal (bit, position);
                in
                    iter (bits, ind + 1, acc + decimal_value)
                end
            else
                acc;
    in
        iter (bit_vector, 0, 0)
    end;


fun sum_bits_at_index (bitss, index) =
    List.foldli (fn (ind, bits, acc) => acc + Vector.sub (bits, index))
                0
                bitss;

fun even num = (num mod 2) = 0;
fun odd num = (num mod 2) = 1;

fun get_least_common_bit (bitss, index) =
    let
        val _ = print "getting least common bit\n";
        val len = length bitss;
        val half_len = (len div 2);
        val sum_of_bits = sum_bits_at_index (bitss, index);
    in
        (* CASE 1: all bits are 1 *)
        (* if there are only 1s then the interpretation is, that 1 is the
           least common and most common bit, instead of 0 being the least
           common bit (such as: a hundred 1s, zero 0s, then 1 instead of 0) *)
        if sum_of_bits = len
        then 1
        else
            if even len
            then
                if sum_of_bits >= half_len
                then 0
                else 1
            else
                if sum_of_bits > half_len
                then 0
                else 1
    end;


(* assertions *)
if 1 = get_least_common_bit ([#[0,0,0], #[1,1,0], #[0,1,1]], 0)
then true
else raise Fail "assertion failed - get_least_common_bit - 01";

if 0 = get_least_common_bit ([#[1,0,0], #[1,1,0], #[0,1,1]], 0)
then true
else raise Fail "assertion failed - get_least_common_bit - 02";

if 0 = get_least_common_bit ([#[0,0,0], #[1,1,0], #[0,1,1], #[1,1,1]], 0)
then true
else raise Fail "assertion failed - get_least_common_bit - 03";

if 1 = get_least_common_bit ([#[1,0,0], #[1,1,0]], 0)
then true
else raise Fail "assertion failed - get_least_common_bit - 04";


fun get_most_common_bit (bitss, index) =
    let
        val _ = print "getting most common bit\n";
        val len = length bitss;
        val _ = print ("num of bit vectors: " ^ (Int.toString len) ^ "\n");
        val half_len = (len div 2);
        val sum_of_bits = sum_bits_at_index (bitss, index);
        val _ = print ("sum of bits at index: " ^ (Int.toString sum_of_bits) ^ "\n");
    in
        (* if there are only 1s then the interpretation is, that 1 is the
           least common and most common bit, instead of 0 being the least
           common bit (such as: a hundred 1s, zero 0s, then 1 instead of 0) *)
        if even len
        then
            if sum_of_bits >= half_len
            then 1
            else 0
        else
            if sum_of_bits > half_len
            then 1
            else 0
    end;


(* assertions *)
if 0 = get_most_common_bit ([#[0,0,0], #[1,1,0], #[0,1,1]], 0)
then true
else raise Fail "assertion failed - get_most_common_bit - 01";

if 1 = get_most_common_bit ([#[1,0,0], #[1,1,0], #[0,1,1]], 0)
then true
else raise Fail "assertion failed - get_most_common_bit - 02";

if 1 = get_most_common_bit ([#[0,0,0], #[1,1,0], #[0,1,1], #[1,1,1]], 0)
then true
else raise Fail "assertion failed - get_most_common_bit - 03";

if 1 = get_most_common_bit ([#[1,0,0], #[1,1,0]], 0)
then true
else raise Fail "assertion failed - get_most_common_bit - 04";

if 0 = get_most_common_bit ([#[0,0,0], #[0,1,0]], 0)
then true
else raise Fail "assertion failed - get_most_common_bit - 05";


fun calculate_rating (bitss, bit_value_selector) =
    let
        val num_bits = Vector.length (hd bitss);
    in
        let
            fun iter ((bitss: int vector list), index) =
                if index < num_bits
                then
                    let
                        val _ = print ("remaining vectors: " ^ Int.toString (length bitss) ^ "\n");
                        val selected_bit_value = bit_value_selector (bitss, index);
                        val _ = print ("selected bit value: " ^ Int.toString selected_bit_value ^ "\n\n");

                        val with_selected_bit =
                            List.filter (fn bits => Vector.sub(bits, index) = selected_bit_value)
                                        bitss;
                    in
                        iter (with_selected_bit, index + 1)
                    end
                else
                    if List.null bitss
                    then raise Fail "list of bit vectors was empty somehow"
                    else hd bitss
        in
            iter (bitss, 0)
        end
    end;


val str = read "input";
val lines = newline_tokenizer str;
val lines_as_bits =
    map (fn line => Vector.fromList (string_to_bits line))
        lines;

val oxygen_gen_rating_bits: int vector = calculate_rating (lines_as_bits, get_most_common_bit);
val oxygen_gen_rating = bits_to_decimal oxygen_gen_rating_bits;

val co2_scrubber_rating_bits = calculate_rating (lines_as_bits, get_least_common_bit);
val co2_scrubber_rating = bits_to_decimal co2_scrubber_rating_bits;

val solution = oxygen_gen_rating * co2_scrubber_rating;

solution;
